﻿using System;
using System.ServiceModel;
using CommonUtils;

namespace SearchClient
{
    using FrontEndServerList = ServerList<SearchFrontEndClient, ISearchFrontEnd>;

    /// <summary>
    /// Implements the client as described in the exercise.
    /// </summary>
    class SearchClient
    {
        private static void PrintBadArgsMsg(string msg)
        {
            Console.WriteLine("Bad arguments: {0}", msg);
            Console.WriteLine("Usage: Client.exe <front end 1 partial URI#front end 2 partial URI#...#front end n partial URI>"); 
        }

        /// <summary>
        /// Performs a search operation. The client attempts to access the FE
        /// servers in RR order.
        /// </summary>
        /// <param name="words">Keywords to search for.</param>
        /// <returns>Search results.</returns>
        private static string[] DoSearch(String[] words)
        {
            Print.DebugEmit(
                        "Doing search on words: " + 
                        Print.ArrayAsString(words)
                        );

            // Get the front end server that is assigned to 
            // handle the current search request.
            SearchFrontEndClient client = FrontEndServerList.GetNext();
            String[] res = null;
            while (res == null)
            {
                try
                {
                    Print.DebugEmit(
                        String.Format(
                            "Attempting to send search request to FE {0}.",
                            client.Endpoint.Address)
                        );
                    res = client.Search(words);
                }
                catch 
                {
                    // if an exception occurs when accessing the FE server
                    // we assume that it is down and remove it from the list.
                    // Then try approaching the next FE server.
                    SearchFrontEndClient tmp = client;
                    client = FrontEndServerList.GetNext();
                    FrontEndServerList.Remove(tmp);

                    if (client.Endpoint.Equals(tmp.Endpoint))
                    {
                        // no more FE servers available
                        Print.DebugEmit("No more servers available, returning..");
                        throw new Exception("Out of available servers.");
                    }
                }
            }
            return res;
        }

        /// <summary>
        /// Performs an index operation. The client attempts to access the FE
        /// servers in RR order.
        /// </summary>
        /// <param name="page">The page to be indexed.</param>
        private static void DoIndex(SearchFrontEnd.Page page)
        {
            // Forward the index request to some BE server, chosen in RR order.
            if (FrontEndServerList.IsEmpty())
            {
                // no more FE servers available
                Print.ColorEmit(
                        "No more servers available, terminating",
                        Print.Foreground.Red
                        );
                throw new Exception("Out of available servers.");
            }

            bool done = false;

            // Get the front end server that is assigned to 
            // handle the current search request.
            SearchFrontEndClient client = FrontEndServerList.GetNext();

            //iterate until we manage to contact a valid FE server
            //and index the page
            while (!done)
            {
                Print.DebugEmit(
                    String.Format(
                        "Trying to forwarded index request to FE {0}...",
                        client.Endpoint.Address
                        )
                    );

                try
                {
                    client.Index(page);
                }
                catch
                {
                    // if an exception occurs when accessing the FE server
                    // we assume that it is down and remove it from the list.
                    // Then try approaching the next BE server.
                    SearchFrontEndClient tmp = client;
                    client = FrontEndServerList.GetNext();
                    FrontEndServerList.Remove(tmp);
                    if (client.Endpoint.Equals(tmp.Endpoint))
                    {
                        // no more FE servers available
                        Print.DebugEmit("No more servers available, terminating..");
                        throw new Exception("Out of available servers.");
                    }
                    continue;
                }
                done = true;
            }
        }

        /// <summary>
        /// main function of the search client.
        /// </summary>
        /// <param name="args">
        /// 1) A concatenation of the partial uri's of the front end servers.
        /// </param>
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                PrintBadArgsMsg("This application receives one argument.");
                return;
            }

            // parse the command line arguments and create the 
            // front end servers' list.
            char[] separator = new char[1]{'#'};
            string[] uriList = args[0].Split(separator);
            foreach (string uri in uriList)
            {
                Uri address = null;
                try
                {
                   address = new Uri("http://" + uri);
                }
                catch (UriFormatException)
                {
                    PrintBadArgsMsg("An invalid URI was given.");
                    return;
                }

                EndpointAddress endpoint = new EndpointAddress(address.AbsoluteUri);
                FrontEndServerList.AddServer(
                                    "BasicHttpBinding_ISearchFrontEnd",
                                    endpoint
                                    );
            }

            String input = "";
            Console.WriteLine("Console command: ");
            Console.WriteLine("-----------------");
            Console.WriteLine("index <file path> - adds file to the index");
            Console.WriteLine("search <Word1> <Word2> ... <WordN> - runs a search query for given words.");
            Console.WriteLine("exit - quits.");
            Console.Write("Enter Command: > ");
            while (!FrontEndServerList.IsEmpty() &&
                  (input = Console.ReadLine()) != "exit")
            {
                String[] inWords = input.Split(' ');
                if (inWords[0] == "debug")
                {
                    FrontEndServerList.DebugPrint();
                }
                //Index operation.
                else if (inWords[0] == "index" && inWords.Length == 2)
                {
                    try
                    {
                        using (System.IO.StreamReader sr = System.IO.File.OpenText(inWords[1]))
                        {
                            string s = sr.ReadLine();
                            SearchFrontEnd.Page p = new SearchFrontEnd.Page();
                            p.Name = inWords[1];
                            p.Content = s;
                            Console.WriteLine("Indexing " + inWords[1] + "...");
                            DoIndex(p);
                            Print.ColorEmit("Done!", Print.Foreground.Blue);
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Error - " + e.Message);
                    }
                }

                //search operation
                else if (inWords[0] == "search" && inWords.Length >= 2)
                {
                    String[] searchWords = new String[inWords.Length - 1];
                    Array.Copy(inWords, 1, searchWords, 0, searchWords.Length);
                    Console.WriteLine("Searching ...");
                    String[] res = null;
                    try
                    {
                        res = DoSearch(searchWords);
                        if (res != null && res.Length > 0)
                        {
                            Console.WriteLine("Found " + res.Length + " Result" + (res.Length > 1 ? "s:" : ":"));
                            for (int i = 0; i < res.Length; i++)
                            {
                                Console.Out.WriteLine((i + 1) + ") " + res[i]);
                            }  
                        }
                        else
                        {
                            Console.WriteLine("No results found!");
                        }
                    }
                    catch (System.Exception e)
                    {
                        Console.WriteLine("Error encountered while searching - " + e.Message);
                    }
                }
                else
                {
                    Console.WriteLine("Unknown command or illegal command format");
                }
                Console.Write("\nEnter Command: > ");
            }
            
            //close all clients
            FrontEndServerList.CloseAll();
        }
    }
}
